fix(spec): KnowledgeRefreshPolicy.cron no longer attributes the syntax verdict to a cron engine that never sees it - #16527
Conversation
…tax verdict to a cron engine that never sees it The docblock, the shipped `.describe()` and the slot's two pin-test comments all said cron syntax was "the `cron` dialect engine's verdict when the expression is evaluated". Both halves were false: nothing evaluates `refresh.cron` (`service-knowledge` reads `refresh.onRecordChange` and never `refresh.cron`), and `@objectstack/formula`'s registered `cron` engine has no caller outside that package. The claim shipped to authors through the generated reference page. All four sites now match the already-corrected Expression Protocol dialect table: no engine evaluates this slot — `croner` judges a cron pattern only where a schedule is wired (`CronSchedule.expression`, a different slot) — so the verdict belongs to whatever external scheduler the author hands the value to. The pin test's behaviour is untouched; only its comments were wrong, and the self-maintaining rule that block ends with is preserved verbatim. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 130 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin ca3544942d328a9a7a909b7db7dd0aefa6631c74 && git checkout ca3544942d328a9a7a909b7db7dd0aefa6631c74
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3335bd35eaf47a5b2aed9120e217dafd242ff7e1 f7a9b0f901101efa494602c48fc702d2bea1b7f6 && git checkout -B drift-repro 3335bd35eaf47a5b2aed9120e217dafd242ff7e1 && git merge --no-ff f7a9b0f901101efa494602c48fc702d2bea1b7f6
node scripts/docs-audit/affected-docs.mjs --json 3335bd35eaf47a5b2aed9120e217dafd242ff7e1 |
Fixes #15867
Clause-②: no— no exported symbol moves, no authorable key moves, no accept-set moves. Parse behaviour is byte-for-byte unchanged. Both surface gates were confirmed at exit 0 and ablated to prove they can go red (below), so the green is a measurement rather than a bare reading.What was false on
mainpackages/spec/src/ai/knowledge-source.zod.tstold authors that cron syntax onKnowledgeRefreshPolicy.cron"is thecrondialect engine's verdict when the expression is evaluated". Both halves were false, and the sentence is customer-facing — it is thecronrow of the generatedcontent/docs/references/ai/knowledge-source.mdx(lines 68 and 133).Re-measured on this branch's base
a5eccf9257, not inherited:git grep -n -E 'refresh\?\.\s*cron|refresh\.cron'outsidepackages/specreturns one hit, and it is a ledger note.service-knowledge's onlyrefreshreads aresource.refresh?.onRecordChange(knowledge-service.ts:396). Nothing evaluates this slot.git grep -n -E 'cronEngine|cron-engine'outsidepackages/formulareturns prose only — a CHANGELOG line, the D7 conformance ledger, and the three sites this PR fixes. Positive control: the same pattern insidepackages/formulareturns the definition (cron-engine.ts:63), the tests and the import sites.The one evaluator any spec cron slot actually meets is
croner, onCronSchedule.expression— a different slot.The four sites, re-located by phrase and not by line number
The card's line numbers had already drifted once, so every site was re-found with the single-line-safe anchor
dialect engine(the fuller phrase wraps across comment lines and matches on no single line).a5eccf9257:35-38:36.describe():45-49:49:17-20:21:96-98:106A deliberate multi-line scan (comment markers stripped, whitespace collapsed, whole tree) confirms the population is exactly these four source sites plus the two generated
.mdxrows.packages/spec/CHANGELOG.mdalso carries the old wording and is deliberately left alone — it is a record of what shipped.What they say now
All four now match the wording of the already-landed sibling correction in
packages/spec/src/shared/expression.zod.ts:25-30: cron syntax is not judged at parse time and no engine evaluates this slot —cronerjudges a cron pattern only where a schedule is wired (CronSchedule.expression, a different slot), and@objectstack/formula's registeredcronengine has no caller outside that package — so the verdict belongs to whatever external scheduler the author hands the value to.Three boundaries held deliberately:
'not a cron'and does not start to; only the two comments were wrong.:21is corrected, not deleted. It ends with a self-maintaining rule — that the pin is what keeps the describe honest, and that if the shared dialect ever gains parse-time syntax validation the pin flips and the describe must be rewritten in the same commit. That rule is true and is preserved verbatim; only the attribution half of the block changed.This correction is safe to write now rather than obsolete on arrival: re-confirmed on base that
expression.zod.ts:25and:267both still readNo cron syntax is judged at parse time, so no parse-time verdict has landed. The parent card #15035 is no longer open, and the PR that resolved it fixed the envelope arm and blank-string refusal — not a syntax verdict.Generated artifacts this change moves — measured, not estimated
Measured by generating from the base source, snapshotting, restoring to this branch's source (restore proven by blob hash equal to the
HEADblob and an emptygit diff HEADfor that path), and generating again:content/docs/references/ai/knowledge-source.mdx— the only artifactcheck:generatedproved stale; regenerated withcheck:generated --fix, never hand-edited.packages/spec/json-schema/:ai/KnowledgeRefreshPolicy.json,ai/KnowledgeSource.json,objectstack.json.Population of that gitignored tree is unchanged at 1576 files — three contents move, nothing is added or removed.
Verification
All readings below are from the final commit
f7a9b0f901; exit codes were captured before any pipe.Tests and types
pnpm --filter @objectstack/spec test→ 483 test files, 13136 tests, all passed,VERDICT command-exit 0from the shared verify lock.pnpm --filter @objectstack/spec typecheck→ exit 0. Itscheck:test-typecheckleg confirms the test layer really compiles (54 file(s) / 261 error(s) / 145 pinned signature(s)held in the shrink-only debt ledger), so the edited test file is genuinely type-checked and not silently excluded.Clause-② gates, with ablations
check:api-surfaceapi-surface/ai.json→ exit 1, "0 breaking (removed/narrowed), 1 added."check:authorable-surfaceai/KnowledgeRefreshPolicy:cronfromauthorable-surface/ai.json→ exit 1,+ ai/KnowledgeRefreshPolicy:cronEach ablation ran under a restore trap with absolute paths; each mutation was proven on disk (line count and blob hash both moved) and each restore proven by blob hash equal to the
HEADblob and an emptygit diff HEAD.Gate families
Derived mechanically with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(change set taken from the merge base, three-dot), then re-derived after the changeset existed, which added six more. All 97 runnable derived commands were run: 95 exit 0, 2 not measured (named below). The 95 includecheck:generated(15 artifacts),check:nul-bytes,check:api-surface,check:authorable-surface,check:docs,check:type-check-coverage,check:cross-package-test-inputs,check:test-source-alias,check:skill-examples(all three prose surfaces, after building@objectstack/client-reactso its verdict is not a false green) and the four changeset families. Two roster-block gates were run on top:check-changeset-fixed.mjsandcheck-skills-token-ratchet.mjs, both exit 0.pnpm lint(eslint . --no-inline-config) ran repo-wide rather than narrowed: 6273 files linted, 0 errors, 0 warnings, exit 0.Not measured (never read as a pass and never as a finding):
check:dual-build-cjs-loadsexits 3,PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/;check:type-check-debtis a whole-farm aggregate that did not finish inside the foreground budget. Both belong to CI, which builds everything.check:react-declaration-paritycannot run here at all — its right-hand side is objectui'ssdui.manifest.json.Byte discipline:
grep -naPover the edited files for control characters returns no hits, andcheck:nul-bytesis green.Generated by Claude Code
Generated by Claude Code